package k8s

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-logr/logr"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type StatefulSet interface {
	GetStatefulSet(namespace, name string) (*appsv1.StatefulSet, error)
	CreateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error
	UpdateStatefulSet(namespace string, sts *appsv1.StatefulSet) error
	DeleteStatefulSet(namespace, name string) error
	CreateOrUpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error
}

type statefulSetService struct {
	kubeClient kubernetes.Interface
	logger     logr.Logger
}

// newStatefulSetService returns a new StatefulSet KubeService.
func newStatefulSetService(kubeClient kubernetes.Interface, logger logr.Logger) *statefulSetService {
	logger = logger.WithValues("service", "k8s.statefulSet")
	return &statefulSetService{
		kubeClient: kubeClient,
		logger:     logger,
	}
}

// CreateOrUpdateStatefulSet will update the statefulset or create it if does not exist
func (s *statefulSetService) CreateOrUpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error {
	storeStatefulSet, err := s.GetStatefulSet(namespace, statefulSet.Name)
	if err != nil {
		// if not exist resource, create it
		if errors.IsNotFound(err) {
			return s.CreateStatefulSet(namespace, statefulSet)
		}
		return err
	}
	// Already exist, update
	switch checkSts(storeStatefulSet, statefulSet) {
	case Update:
		statefulSet.ResourceVersion = storeStatefulSet.ResourceVersion
		return s.UpdateStatefulSet(namespace, statefulSet)
	case Delete:
		return s.DeleteStatefulSet(namespace, statefulSet.Name)
	}

	return nil
}

// GetStatefulSet get statefulset for namespace and name
func (s *statefulSetService) GetStatefulSet(namespace, name string) (*appsv1.StatefulSet, error) {
	statefulSet, err := s.kubeClient.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return statefulSet, err

}

func (s *statefulSetService) CreateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error {
	_, err := s.kubeClient.AppsV1().StatefulSets(namespace).Create(context.TODO(), statefulSet, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	s.logger.WithValues("namespace", namespace).WithValues("statefulSet", statefulSet.Name).Info("statefulSet created")
	return err
}

func (s *statefulSetService) UpdateStatefulSet(namespace string, sts *appsv1.StatefulSet) error {
	_, err := s.kubeClient.AppsV1().StatefulSets(namespace).Update(context.TODO(), sts, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	s.logger.WithValues("namespace", namespace).WithValues("statefulSet", sts.Name).Info("statefulSet updated")
	return err
}

func (s *statefulSetService) DeleteStatefulSet(namespace, name string) error {
	// Foreground: 删除控制器之前先删除被管理的资源对象
	// Orphan: 只删除控制器
	// Background: 删除控制器后被控制的对象由GC后台删除
	propagation := metav1.DeletePropagationForeground
	return s.kubeClient.AppsV1().StatefulSets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{PropagationPolicy: &propagation})
}

type operator int

const (
	None operator = iota
	Update
	Delete
)

func checkSts(old *appsv1.StatefulSet, new *appsv1.StatefulSet) operator {
	rsA, _ := json.Marshal(old.Spec.Template.Spec.Containers[0].Resources)
	rsB, _ := json.Marshal(new.Spec.Template.Spec.Containers[0].Resources)

	envA, _ := json.Marshal(old.Spec.Template.Spec.Containers[0].Env)
	envB, _ := json.Marshal(new.Spec.Template.Spec.Containers[0].Env)

	if !bytes.Equal(rsA, rsB) || *old.Spec.Replicas != *new.Spec.Replicas || !bytes.Equal(envB, envA) {
		fmt.Println("bytes")
		return Update
	}
	return None
}
